﻿using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace Common.Framework.Core.Jwt
{
    public class JwtConfiguration
    {
        private readonly IConfiguration Configuration;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="configuration"></param>
        public JwtConfiguration(IConfiguration configuration)
        {
            this.Configuration = configuration;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="claims">添加自定义Claim</param>
        /// <param name="expireMinutes">token过期时间,该值若被设置 则以设置的为准,若不设置 则以配置项中配置的为准</param>
        /// <returns></returns>
        public string GenerateToken(string userName, List<Claim> claimList = null, int expireMinutes = 0)
        {
            var issuer = Configuration.GetValue<string>("JwtSettings:Issuer");
            var signKey = Configuration.GetValue<string>("JwtSettings:SignKey");
            var expireMinute = expireMinutes > 0 ? expireMinutes : Configuration.GetValue<int>("JwtSettings:ExpireMinutes");

            //设定要加入到 JWT Token 中的声明信息(Claims)
            var claims = new List<Claim>();

            // 在 RFC 7519 标准中(Section#4)，总共定义了 7 个预设的 Claims，我们应该只用的到两种！
            // 注释 claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer))
            claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userName)); // User.Identity.Name
            // claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "The Audience"))
            // claims.Add(new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds().ToString()))
            // claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必须为数字
            // claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必须为数字
            claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // JWT ID

            // 网络上常看到的这个 NameId 设定是多余的
            // claims.Add(new Claim(JwtRegisteredClaimNames.NameId, userName))

            // 这个 Claim 也以直接被 JwtRegisteredClaimNames.Sub 取代，所以也是多余的
            // claims.Add(new Claim(ClaimTypes.Name, userName))

            // 你可以自行扩充 "roles" 加入登入者该有的角色

            // claims.Add(new Claim("roles", "Admin"))
            // claims.Add(new Claim("roles", "Users"))
            if (claimList != null)
            {
                claimList.ForEach(s =>
                {
                    claims.Add(s);
                });
            }

            var userClaimsIdentity = new ClaimsIdentity(claims);
            // 建立一组对称加密的秘钥 ，主要用于 JWT 签名之用
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey));

            // HmacSha256 有要求必须要大于 128 bits，所以 key 不能太短，至少要 16 字节以上
            // https://stackoverflow.com/questions/47279947/idx10603-the-algorithm-hs256-requires-the-securitykey-keysize-to-be-greater
            var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

            // 建立 SecurityTokenDescriptor
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Issuer = issuer, //发行人
                //Audience = issuer, // 由于你的 API 受众通常没有区分特别对象，因此通常不太需要设定，也不太需要验证
                //NotBefore = DateTime.Now, // 预设值就是 DateTime.Now
                //IssuedAt = DateTime.Now, // 预设值就是 DateTime.Now
                Subject = userClaimsIdentity, //Token的身份证，类似一个人可以有身份证，户口本
                Expires = DateTime.Now.AddMinutes(expireMinute), //Token 有效期
                SigningCredentials = signingCredentials //生成一个Token证书，第一个参数是根据预先的二进制字节数组生成一个安全秘钥，说白了就是密码，第二个参数是编码方式
            };

            // 产出所需要的 JWT securityToken ，并取得序列化后的 Token 结果(字符串格式)
            var tokenHandler = new JwtSecurityTokenHandler();
            var securityToken = tokenHandler.CreateToken(tokenDescriptor);
            var serializeToken = tokenHandler.WriteToken(securityToken);

            return serializeToken;
        }

    }
}
